{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.sys.path.append(os.path.dirname(os.path.abspath('.')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from datasets.dataset import load_iris\n",
    "from model_selection.train_test_split import train_test_split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = load_iris()\n",
    "X = data.data\n",
    "Y = data.target\n",
    "\n",
    "X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据总览\n",
    "对于朴素贝叶斯，预测概率可以写成：\n",
    "$$\n",
    "P(y=c_{i}|x)=\\frac{P(x|y=c_{i})P(y=c_{i})}{P(x)}\n",
    "$$\n",
    "可以证明对同一数据集，$P(x)$是一个常量(详见博客)，$P(y=c_{i})$也好算，关键在于$P(x|y=c_{i})$的计算。在iid条件下，$P(x|y=c_{i})$可以写成：\n",
    "$$\n",
    "P(x|y=c_{i})=P(x_{0}^{D}=x_{0}|y=c_{i})...P(x_{m}^{D}=x_{m}|y=c_{i})\n",
    "$$\n",
    "转变成了计算某类别下某特征的取值分布。对于离散型特征与连续性特征都可以设置对应的预定概率分布。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # 首先计算类别class的先验分布\n",
    "# def cls_prob(Y):\n",
    "#     uni_val, cnt = np.unique(Y, return_counts=True)\n",
    "#     return np.array([cnt[idx]/np.sum(cnt) for idx in range(len(uni_val))])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然后需要计算每个类别下每个特征的分布概率。对于连续特征，我们假定其服从高斯分布，那么就需要先计算各类别下各特征的均值与标准差。假设用矩阵来存储训练数据的统计值，把同一类的数值存储到一行，则该矩阵的维度为$(n\\_class,n\\_feature)$。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# def mean_std(X_train, Y_train):\n",
    "#     uni_cls = np.unique(Y_train)\n",
    "#     m_feature = X_train.shape[1]\n",
    "\n",
    "#     mean_mat = np.array(\n",
    "#         [X_train[np.where(Y_train == cls)].mean(axis=0) for cls in uni_cls])\n",
    "#     std_mat = np.array(\n",
    "#         [X_train[np.where(Y_train == cls)].std(axis=0) for cls in uni_cls])\n",
    "\n",
    "#     return mean_mat, std_mat"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有了mean-std数据之后，就可以很方便地计算$P(x|y=c_{i})$了。一个样本对应到每个类别都有一个概率，所以维度为$(n\\_sample,n\\_class)$。示例数据为连续型特征，假设$P(x_{i}|y=c_{k})=\\frac{1}{\\sqrt{2\\pi}\\sigma_{c_{k},i}}exp(-\\frac{(x_{i}-\\mu_{c_{k},i})^{2}}{2\\sigma_{c_{k},i}^{2}})$。在实现时考虑到小数连乘容易下溢，还有就是如果某一子概率为零就会导致连乘结果为零，故转成对数相加。下面函数为计算单个样本的后验概率，返回结果的维度为$(1,n\\_class)$。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # 计算x的后验条件概率，考虑到小数累乘容易下溢出，转为对数运算\n",
    "# def post_prob(x_test, mean, std):\n",
    "#     return np.log2(1/(np.sqrt(2*np.pi)*std)*np.exp(-np.square(x_test-mean)/(2*np.square(std)))).sum(axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "需要计算的概率都有了之后，就可以进行预测了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# def predict(X_test,mean,std,Y_prob):\n",
    "#     # 之前计算x的后验概率时用了对数转换，这里在加上类概率时也转成对数相加\n",
    "#     Y_pred=[np.argmax(post_prob(item,mean,std)+np.log2(Y_prob)) for item in X_test]\n",
    "#     return np.array(Y_pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 封装\n",
    "模型的功能都实现好之后，进行sklearn-like的封装。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "acc:0.9333333333333333\n"
     ]
    }
   ],
   "source": [
    "class GaussianNB:\n",
    "    def __init__(self):\n",
    "        self.Y_prob = None\n",
    "        self.mean = None\n",
    "        self.std = None\n",
    "\n",
    "    # 计算类分布概率\n",
    "    def __cls_prob(self, Y_train):\n",
    "        uni_val, cnt = np.unique(Y, return_counts=True)\n",
    "        self.Y_prob = np.array([cnt[idx]/np.sum(cnt)\n",
    "                                for idx in range(len(uni_val))])\n",
    "\n",
    "    # 计算各特征在各类别下的统计值\n",
    "    def __mean_std(self, X_train, Y_train):\n",
    "        uni_cls = np.unique(Y_train)\n",
    "        m_feature = X_train.shape[1]\n",
    "\n",
    "        self.mean = np.array(\n",
    "            [X_train[np.where(Y_train == cls)].mean(axis=0) for cls in uni_cls])\n",
    "        self.std = np.array(\n",
    "            [X_train[np.where(Y_train == cls)].std(axis=0) for cls in uni_cls])\n",
    "\n",
    "    # 训练函数，实质就是计算训练数据中的一些统计量\n",
    "    def fit(self, X_train, Y_train):\n",
    "        self.__cls_prob(Y_train)\n",
    "        self.__mean_std(X_train, Y_train)\n",
    "\n",
    "    def __post_prob(self, x_test):\n",
    "        return np.log2(1/(np.sqrt(2*np.pi)*self.std)*np.exp(-np.square(x_test-self.mean)/(2*np.square(self.std)))).sum(axis=1)\n",
    "\n",
    "    def predict(self, X_test):\n",
    "        return np.array([np.argmax(self.__post_prob(item)+np.log2(self.Y_prob)) for item in X_test])\n",
    "\n",
    "\n",
    "gnb = GaussianNB()\n",
    "gnb.fit(X_train, Y_train)\n",
    "Y_pred = gnb.predict(X_test)\n",
    "print('acc:{}'.format(np.sum(Y_pred == Y_test)/len(Y_test)))"
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
